use std::fs;
use std::net::SocketAddr;
use std::sync::atomic::AtomicU16;
use std::sync::{Arc, RwLock};

use axum::body::Body;
use axum::extract::Multipart;
use axum::http::Response;
use axum::response::Html;
use axum::response::IntoResponse;
use axum::Form;

use axum::response::Redirect;
use axum::{
    extract::Path,
    extract::State,
    http::{StatusCode, Uri},
    routing::{delete, get, post},
    Json, Router,
};

use axum_hello::scheduler_job::article_read_time_scheduler_job::ArticleReadTimeSchedulerJob;

use axum::http::HeaderValue;
use axum_server::tls_openssl::OpenSSLConfig;
use sqlx::mysql::MySqlPoolOptions;
use sqlx::MySqlPool;
use tower_http::cors::CorsLayer;

use job_scheduler::Uuid;

use tklog::{debug, error, info, warn};

use chrono::{DateTime, Duration, Utc};

pub mod configuration;
pub mod entity;
pub mod job;
pub mod layer;
pub mod person;
pub mod session;

use crate::configuration::app_config::AppConfig;
use crate::entity::article::Article;
use crate::entity::article_detail::ArticleDetail;
use crate::entity::article_list_view::ArticlesListView;
use crate::entity::article_summary::ArticleSummary;
use crate::entity::article_summary_vec::ArticleSummaryVec;
use crate::entity::login_form::LoginForm;
use crate::entity::news::News;
use crate::entity::output::Output;
use crate::entity::stock_index::StockIndex;
use crate::person::Person;

use crate::layer::log_layer::LogLayer;
use crate::layer::session_layer::SessionLayer;
use crate::layer::url_layer::UrlLayer;

use crate::session::session::Session;
use crate::session::session_manage::SessionManager;

use crate::job::file_extractor::Extractor;

#[tokio::main]
async fn main() {
    //读取配置文件
    let _ = AppConfig::init();
    let app_config = AppConfig::get();
    //初始化日志配置
    configuration::log_config::log_init();

    let workspace = std::env::current_dir().unwrap();
    println!("{:?}", &workspace);
    info!("Current workspace:: {:?}", &workspace.to_string_lossy());

    let pool = MySqlPoolOptions::new()
        .connect(&app_config.get_database_connection_url())
        .await
        .expect("failed to connect database.");

    // default page_size=9
    let default_vec_size = 9;
    let count_vec = (0..default_vec_size)
        .map(|_| AtomicU16::new(0))
        .collect::<Vec<_>>();
    let articles_click_vec: Arc<RwLock<Vec<AtomicU16>>> = Arc::new(RwLock::new(count_vec));
    let url_layer = UrlLayer::new(Arc::clone(&articles_click_vec));
    let log_layer = LogLayer {
        target: "hello layer",
    };
    let timeout = AppConfig::get().get_session_timeout();
    let _ = SessionManager::init(timeout).expect("Initialized SessionManager failed!");
    let session_layer = SessionLayer::new(SessionManager::get());
    let router = Router::new()
        .route("/blog/new", post(upload_file))
        .route("/toupload", get(upload_page))
        .route("/", get(articles_view)) //只返回试图,路由作用
        .route("/articles", get(articles_view)) //只返回试图，路由作用
        .route("/articles/:page_num", get(article_list)) //返回具体信息
        .route("/article/:id", get(article_detail))
        .route("/me", get(me))
        .route("/tologin", get(to_login))
        .route("/login", post(login))
        .route("/article/new", get(extract_article))
        .route("/market", get(market_info))
        .route("/market/news/date/:date", get(info_by_date))
        .route("/market/news/:id", delete(delete_info_by_id))
        .route("/market/stock/:date", get(stock_index_by_date))
        .route("/market/stock/:id/:flag", get(stock_info_by_id))
        .route("/market/management", get(manage_market))
        .route("/market/stock/index", post(update_stock_index))
        .route("/market/stock/news", post(create_stock_info))
        .fallback(status_sode_404)
        .layer(
            //设置同源策略
            CorsLayer::new().allow_origin("*".parse::<HeaderValue>().unwrap()), //need to fix
        )
        .layer(log_layer)
        .layer(url_layer)
        .layer(session_layer)
        .with_state(pool.clone())
        .nest_service("/assets", tower_http::services::ServeDir::new("assets"))
        .nest_service(
            "/favicon.ico",
            tower_http::services::ServeDir::new("favicon.ico"),
        );

    //开启scheudler
    ArticleReadTimeSchedulerJob::new(Arc::clone(&articles_click_vec), pool.clone()).start();

    // run our app with hyper, listening globally on port 3000
    /*
    let listener = tokio::net::TcpListener::bind(&app_config.get_server_url())
        .await
        .unwrap();
    axum::serve(listener, router).await.unwrap();
    */

    let certification_config = match OpenSSLConfig::from_pem_file(
        app_config.get_certification().0,
        app_config.get_certification().1,
    ) {
        Ok(config) => config,
        Err(e) => {
            error!("Failed to load SSL certificates: {}", e);
            panic!("SSL certificate error: {}", e);
        }
    };

    println!("cetification: {:?}", &certification_config);

    let addr = SocketAddr::from(([0, 0, 0, 0], app_config.get_server_port()));
    println!("Listening on {}", addr);
    axum_server::bind_openssl(addr, certification_config)
        .serve(router.into_make_service())
        .await
        .unwrap();
}

#[allow(dead_code)]
async fn say_hello(State(pool): State<MySqlPool>) -> String {
    let row: (String,) = sqlx::query_as("SELECT 'hello world, Rust!'")
        .fetch_one(&pool)
        .await
        .expect("failed!");
    //assert_eq!(row.0, 150);
    row.0
}

async fn status_sode_404(uri: Uri) -> (StatusCode, String) {
    (StatusCode::NOT_FOUND, format!("No route for {uri}"))
}

async fn articles_view() -> ArticlesListView {
    ArticlesListView::new()
}

async fn article_list(
    State(pool): State<MySqlPool>,
    Path(page_num): Path<i32>,
) -> Json<ArticleSummaryVec> {
    info!("page_num: ", page_num);
    let page_size: i32 = 9;
    let offset = (page_num - 1) * page_size;
    info!("the offset is {}", &offset);

    let summary_vec: Vec<ArticleSummary> = sqlx::query_as(
        r#"SELECT a.id, a.title, a.categoryId, a.summary,path,
        a.createOn, a.createBy, IFNull(ac.total_times,0) as view_amount
        FROM article a
        LEFT JOIN access_count ac on ac.article_id=a.id
        WHERE deleted=0
        order by id desc limit ?, ? "#,
    )
    .bind(offset)
    .bind(page_size)
    .fetch_all(&pool)
    .await
    .expect("fetch article list failed!");
    //assert_eq!(row.0, 150);
    let total: i32 = sqlx::query_scalar("select count(id) from article where deleted=0")
        .fetch_one(&pool)
        .await
        .expect("Query page total failed!");
    info!("total: ", &total);

    let mut vec1: Vec<ArticleSummary> = vec![];
    let mut vec2: Vec<ArticleSummary> = vec![];
    let mut vec3: Vec<ArticleSummary> = vec![];
    for (position, article) in summary_vec.iter().enumerate() {
        info!("position: ", position);
        match position % 3 {
            0 => vec1.push(article.clone()),
            1 => vec2.push(article.clone()),
            2 => vec3.push(article.clone()),
            _ => (),
        }
    }
    Json(ArticleSummaryVec::new(vec1, vec2, vec3, total, page_num))
}

async fn article_detail(State(pool): State<MySqlPool>, Path(id): Path<i32>) -> ArticleDetail {
    info!("the article id is ", &id);

    let article: Article = sqlx::query_as("SELECT id, title, categoryId, summary, path, deleted, createOn, createBy, modifyOn, modifyBy FROM article WHERE ID=?")
            .bind(id)
            .fetch_one(&pool).await.expect("Can not find the article detail");

    let (article_id, article_title, article_create_on) = article.clone().get_info();
    let article_detail = ArticleDetail::new(article_id, article_title, vec![], article_create_on);
    let content = article.read_file().await;
    article_detail.set_content(content)
}

pub async fn extract_article(State(pool): State<MySqlPool>) -> String {
    let current_workspace = std::env::current_dir().unwrap().join("markdown");
    let insert_article_result =
        Extractor::visit_dirs(std::path::Path::new(current_workspace.as_os_str()), &pool).await;
    match insert_article_result {
        Ok(_) => "Insert Article successfully.".to_string(),
        Err(_) => "Insert Article failed!".to_string(),
    }
}

async fn me() -> Person<'static> {
    Person { name: "Owen" }
}

async fn upload_page() -> Html<String> {
    let upload_page_html = fs::read_to_string("./templates/html/blog_upload.html").unwrap();
    Html(upload_page_html)
}

async fn upload_file(mut multipart: Multipart) -> &'static str {
    // 循环读取每一个字段
    while let Some(field) = match multipart.next_field().await {
        Ok(Some(field)) => Some(field),
        Ok(None) => {
            return "没有接收到上传的文件，请检查是否选择文件！";
        }
        Err(e) => {
            error!("读取文件字段失败: {}", e);
            return "上传文件失败：字段读取错误！";
        }
    } {
        // 获取文件名
        let file_name = match field.file_name() {
            Some(name) => name.to_string(),
            None => {
                warn!("上传的文件没有文件名");
                return "上传的文件缺少文件名！";
            }
        };

        // 读取文件内容
        let data = match field.bytes().await {
            Ok(bytes) => bytes,
            Err(e) => {
                error!("读取文件内容失败: {}", e);
                return "上传文件失败：读取内容错误！";
            }
        };

        // 构造文件路径
        let file_path_str =
            AppConfig::get().get_file_upload_location().to_string() + "/" + &file_name;
        let file_path = std::path::Path::new(&file_path_str);

        info!(&file_path_str);

        // 写入文件
        if let Err(e) = fs::write(file_path, data) {
            error!("保存文件失败: {}", e);
            return "上传文件失败：保存文件时出错！";
        }

        // 如果只允许上传一个文件，处理完就返回成功
        return "上传成功";
    }

    // 所有字段都已处理完毕，但没有文件被上传
    "没有接收到上传的文件，请检查是否选择文件！"
}

async fn to_login() -> Html<String> {
    let login_page = fs::read_to_string("./templates/html/login.html").unwrap();
    Html(login_page)
}

async fn login(Form(form): Form<LoginForm>) -> Result<Response<Body>, StatusCode> {
    println!("login");
    let app_config = AppConfig::get();
    println!("app_config: {:?}", app_config);
    let admin = app_config.get_admin_settings();
    if admin.username == form.username && admin.password == form.password {
        let mut response = Redirect::to("/").into_response(); //redirect to home page
        let now: DateTime<Utc> = Utc::now();
        let thirty_minutes_later = now + Duration::minutes(30);
        let expire_time = thirty_minutes_later.format("%a, %d %b %Y %T GMT");
        let session_id = Uuid::new_v4().to_string();
        let cookies = format!("sessionid={};Expires={}", session_id, expire_time);

        let session = Session::new(
            session_id.clone(),
            admin.username.clone(),
            admin.password.clone(),
            String::from("Admin"),
        );

        SessionManager::get()
            .add_session(session_id.clone(), session)
            .unwrap();
        response.headers_mut().insert(
            "Set-Cookie",
            HeaderValue::from_str(cookies.as_str()).unwrap(),
        );
        return Ok(response);
    }
    return Err(StatusCode::UNAUTHORIZED);
}

#[axum::debug_handler]
async fn market_info() -> Html<String> {
    let market = fs::read_to_string("./templates/html/market.html").unwrap();
    Html(market)
}

#[axum::debug_handler]
async fn info_by_date(
    State(pool): State<MySqlPool>,
    Path(date): Path<String>,
) -> Output<Vec<News>> {
    debug!("info_by_date: ", date);
    let indexs: Result<Vec<News>, sqlx::Error> =
        sqlx::query_as("SELECT id, date, country, title, content, create_on, create_by, modify_on, modify_by FROM news WHERE date=?")
            .bind(&date)
            .fetch_all(&pool)
            .await;
    match indexs {
        Ok(news_vec) => Output::success(
            "News data retrieved successfully".to_string(),
            Some(news_vec),
        ),
        Err(err) => {
            error!("query news error: ", err);
            Output::failure(
                StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
                "Failed to query news".to_string(),
            )
        }
    }
}

#[axum::debug_handler]
async fn stock_index_by_date(
    State(pool): State<MySqlPool>,
    Path(date): Path<String>,
) -> Output<StockIndex> {
    let indexs: Result<Option<StockIndex>, sqlx::Error> = sqlx::query_as(
        "SELECT id, date, index_at_start, index_at_noon, index_at_end, create_on, create_by, modify_on, modify_by  FROM stock_index WHERE date=?",
    )
    .bind(&date)
    .fetch_optional(&pool)
    .await;
    match indexs {
        Ok(index) => match index {
            None => Output::failure(
                StatusCode::NOT_FOUND.as_u16(),
                format!("Stock index not found at {}", date),
            ),
            Some(mut index) => {
                let index_before: Result<Option<StockIndex>, sqlx::Error> = sqlx::query_as(
                    "SELECT id, date, index_at_start, index_at_noon, index_at_end, create_on, create_by, modify_on, modify_by  FROM stock_index WHERE date<? order by date desc limit 1",
                )
                .bind(&date)
                .fetch_optional(&pool)
                .await;

                match index_before {
                    Ok(index_value) => {
                        if let Some(last) = index_value {
                            index.index_at_last_day = Some(last.index_at_end);
                        } else {
                            index.index_at_last_day = Some(0);
                        }
                    }
                    Err(err) => {
                        error!("query stock index error: ", err);
                    }
                }

                Output::success(
                    "Stock index data retrieved successfully".to_string(),
                    Some(index),
                )
            }
        },
        Err(err) => {
            error!("query stock index error: ", err);
            Output::failure(
                StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
                "Failed to query stock index".to_string(),
            )
        }
    }
}

#[axum::debug_handler]
async fn stock_info_by_id(
    State(pool): State<MySqlPool>,
    Path((date, flag)): Path<(String, String)>,
) -> Output<StockIndex> {
    if date.is_empty()
        || flag.is_empty()
        || (!"before".eq(flag.as_str()) && !"after".eq(flag.as_str()))
    {
        return Output::failure(
            StatusCode::BAD_REQUEST.as_u16(),
            "Invalid id or flag".to_string(),
        );
    }

    /*
    //比较date 和今天的日期，如果date比今天日期还晚，则返回错误
    let today = chrono::Utc::now().date_naive().to_string();
    if date >= today && "after".eq(flag.as_str()) {
        return Output::failure(
            StatusCode::BAD_REQUEST.as_u16(),
            "嘿，明天的数据还没有呢。".to_string(),
        );
    }
    */

    let sql = match flag.as_str() {
        "before" => "SELECT id, date, index_at_start, index_at_noon, index_at_end, create_on, create_by, modify_on, modify_by  FROM stock_index WHERE date < ? order by date desc limit 2",
        "after" => "SELECT id, date, index_at_start, index_at_noon, index_at_end, create_on, create_by, modify_on, modify_by  FROM stock_index WHERE date >= ? order by date asc limit 2",
        _ => unreachable!(),
    };

    let indexs: Result<Vec<StockIndex>, sqlx::Error> =
        sqlx::query_as(sql).bind(&date).fetch_all(&pool).await;

    match indexs {
        Ok(index_vec) => {
            if index_vec.is_empty() {
                Output::failure(
                    StatusCode::NOT_FOUND.as_u16(),
                    "抱歉，没有更多的数据了～_~".to_string(),
                )
            } else {
                if "before".eq(flag.as_str()) {
                    if index_vec.len() == 2 {
                        let index_two_record_before = index_vec[1].clone();
                        let mut index_one_record_before = index_vec[0].clone();
                        index_one_record_before.index_at_last_day =
                            Some(index_two_record_before.index_at_end);
                        Output::success("Success".to_string(), Some(index_one_record_before))
                    } else {
                        let mut index_one_record_before = index_vec[0].clone();
                        index_one_record_before.index_at_last_day = Some(0);
                        Output::success("Success".to_string(), Some(index_one_record_before))
                    }
                } else {
                    if index_vec.len() == 2 {
                        let mut index_one_record_after = index_vec[1].clone();
                        let index_current_record = index_vec[0].clone();
                        index_one_record_after.index_at_last_day =
                            Some(index_current_record.index_at_end);
                        Output::success("Success".to_string(), Some(index_one_record_after))
                    } else {
                        Output::failure(
                            StatusCode::NOT_FOUND.as_u16(),
                            "抱歉，没有更多的数据了~_~".to_string(),
                        )
                    }
                }
            }
        }
        Err(err) => {
            error!("query stock index error: ", err);
            Output::failure(
                StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
                "Failed to query stock index".to_string(),
            )
        }
    }
}

/**
 * json 这种提取器必须放在state后面
 *
 * 如果存在对应记录，则更新，否则则创建记录
 */
#[axum::debug_handler]
async fn update_stock_index(
    State(pool): State<MySqlPool>,
    Json(payload): Json<StockIndex>,
) -> Output<String> {
    //如果存在对应记录，则更新，否则则创建记录
    let query = r#"
            INSERT INTO stock_index
            (id, date, index_at_start, index_at_noon, index_at_end, create_by, modify_by)
            VALUES (?,?, ?, ?, ?, ?, ?)
            ON DUPLICATE KEY UPDATE
            index_at_start = VALUES(index_at_start),
            index_at_noon = VALUES(index_at_noon),
            index_at_end = VALUES(index_at_end)
        "#;

    match sqlx::query(query)
        .bind(payload.id)
        .bind(&payload.date)
        .bind(payload.index_at_start)
        .bind(payload.index_at_noon)
        .bind(payload.index_at_end)
        .bind("Admin")
        .bind("Admin")
        .execute(&pool)
        .await
    {
        Ok(_) => Output::success("Index saved".to_string(), Some("创建成功".to_string())),
        Err(e) => {
            eprintln!("Database error: {}", e);
            Output::failure(500, format!("保存失败: {}", e))
        }
    }
}

#[axum::debug_handler]
async fn create_stock_info(
    State(pool): State<MySqlPool>,
    Json(payload): Json<Vec<News>>,
) -> Output<String> {
    if payload.is_empty() {
        return Output::success(
            "No data to process".to_string(),
            Some("无数据需要处理".to_string()),
        );
    }

    let query = r#"
        INSERT INTO news (id, date, country, title, content, create_by, modify_by)
        VALUES (?, ?, ?, ?, ?, ?, ?)
        ON DUPLICATE KEY UPDATE
            date = VALUES(date),
            country = VALUES(country),
            title = VALUES(title),
            content = VALUES(content),
            modify_by = VALUES(modify_by)
    "#;

    let mut transaction = match pool.begin().await {
        Ok(tx) => tx,
        Err(e) => {
            error!("Failed to begin transaction: ", e);
            return Output::failure(500, "数据库事务开启失败".to_string());
        }
    };

    for news in payload {
        let result = sqlx::query(query)
            .bind(news.id) // 对于新记录，这里是None
            .bind(&news.date)
            .bind(&news.country)
            .bind(&news.title)
            .bind(&news.content)
            .bind("admin")
            .bind("admin")
            .execute(&mut *transaction)
            .await;

        if let Err(e) = result {
            let _ = transaction.rollback().await;
            return Output::failure(500, format!("保存失败: {}", e));
        }
    }

    if let Err(e) = transaction.commit().await {
        error!("Failed to commit transaction: ", e);
        return Output::failure(500, "事务提交失败".to_string());
    }

    Output::success("成功处理".to_string(), Some(format!("成功处理记录")))
}

#[axum::debug_handler]
async fn delete_info_by_id(
    Path(id): Path<String>,
    State(pool): State<MySqlPool>,
) -> Output<String> {
    if id.is_empty() {
        return Output::failure(StatusCode::BAD_REQUEST.as_u16(), "ID不能为空".to_string());
    }

    let query = "DELETE FROM news WHERE id = ?";

    let mut transaction = match pool.begin().await {
        Ok(tx) => tx,
        Err(e) => {
            error!("Failed to begin transaction: ", e);
            return Output::failure(
                StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
                "数据库事务开启失败".to_string(),
            );
        }
    };

    let result = sqlx::query(query).bind(id).execute(&mut *transaction).await;

    if let Err(e) = result {
        let _ = transaction.rollback().await;
        return Output::failure(
            StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
            format!("删除失败: {}", e),
        );
    }

    if let Err(e) = transaction.commit().await {
        error!("Failed to commit transaction: ", e);
        return Output::failure(
            StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
            "事务提交失败".to_string(),
        );
    }

    Output::success("新闻已删除".to_string(), Some(format!("新闻已经成功删除")))
}

#[axum::debug_handler]
async fn manage_market() -> Html<String> {
    let management = fs::read_to_string("./templates/html/market_management.html").unwrap();
    Html(management)
}

#[cfg(test)]
mod test {
    use super::*;
    use sqlx::mysql::MySqlPoolOptions;
    use std::path::Path;

    #[tokio::test]
    async fn test_extract() {
        let path = Path::new("E:\\test\\markdown");

        let pool = MySqlPoolOptions::new()
            .connect("mysql://root:rust(202408)@47.92.236.188/Blog")
            .await
            .expect("failed to connect database.");

        for entry in path.read_dir().expect("read_dir call failed") {
            if let Ok(entry) = entry {
                let _ = Extractor::extract(&entry, &pool);
            }
        }
    }

    #[tokio::test]
    async fn insert_db() {
        info!(">>>>>>>>>>>>>>>>>insert article into db");
        //let mysql_conn = MySqlPool::connect("mysql://username:password@address/Blog").await.expect("Failed to connect to database");

        let mysql_conn = MySqlPoolOptions::new()
            .connect("mysql://username:password@address/Blog")
            .await
            .expect("failed to connect database.");

        let _ = sqlx::query(
                    r#"INSERT INTO article (title, summary, path, createBy, createOn, modifyBy, modifyOn) VALUES ('UT', '鸿蒙系统', 'E:\\test.md',  'Owwww', now(), 'YYYY',now() )"#
                ).execute(&mysql_conn).await;
    }
}
